PostCSS in Action

Алексей Плуталов, Злые Марсиане

PostCSS in Action

logo.svg Алексей Плуталов, Злые Марсиане

martians.svg

Работал над

ebay.svgidinaidi.pngamplifr.png

Проблема

covers/beginning.jpg

Проблема Эволюция

evolution.jpg

Проблема Новые технологии

html5.svg

Проблема Новые инструменты

sass-compass.jpg

Проблема Застой развития

bad-evolution.png

Проблема Новые идеи

tj.jpg Modular CSS preprocessing with rework
@tjholowaychuk, Февраль 2013

PostCSS

covers/postcss.jpg

PostCSS Препроцессоры

preprocessor-scheme.svg

PostCSS Схема работы

postcss-scheme.svg

PostCSS Платформа

postcss.svg

PostCSS Не только постпроцессор

precss.svg

PostCSS Не только трансформирует

Например, stylelint:

#bad-selector {
  background: red;
}
Unexpected id selector (selector-no-id) [stylelint]

PostCSS Не только расширение CSS

$background-color: red;

body {
  background: $background-color;
}

PostCSS и препроцессоры

covers/preprocessors.jpg

PostCSS и препроцессоры Работают вместе

pre-post.svg

PostCSS и препроцессоры Модульность

modularity.svg

PostCSS и препроцессоры Поддержка

Плагин Строк кода
postcss-simple-vars 92
postcss-mixins 186
postcss-nested 73

PostCSS и препроцессоры Производительность

PostCSS уже здесь

covers/there.jpg

PostCSS уже здесь Autoprefixer

autoprefixer.svg

PostCSS уже здесь Большие игроки

logos/google.svglogos/taobao.svglogos/wordpress.svglogos/twitter.svglogos/ok.svglogos/vk.svg

PostCSS уже здесь Количество загрузок

Интеграция

covers/integration.jpg

Интеграция Ручной вызов

import postcss from 'postcss';

postcss([ plugin1, plugin2 ])
  .process(css)
  .then( result => console.log(result.css) );

Интеграция Интеграция с Gulp

gulp.task('css', () => {
  return gulp.src('./components/**/*.css')
    .pipe(postcss([
      autoprefixer({ browsers: [ 'last 2 versions' ] }),
      cssnano
    ])
    .pipe(gulp.dest('./dest'));
});

Интеграция Интеграция с Webpack

export default {
  module: {
    loaders: [ {
      test:   /\.css$/,
      loader: 'style-loader!css-loader!postcss-loader'
    } ]
  },
  postcss () {
    return [ autoprefixer({ browsers: [ 'last 2 versions' ] }),
             cssnano ];
  }
}

Интеграция postcss-use

@use postcss-circle;

.circle {
  circle: 100px red;
}

Внедрение в проект

covers/project.jpg

Внедрение в проект Проект без PostCSS

.pipe(less())
.pipe(postcss([
  require('autoprefixer-core'),
  require('cssnext'),
  require('cssnano')
])

Внедрение в проект Новый проект

.pipe(postcss([
  require('precss'),
  require('autoprefixer-core'),
  require('cssnano')
])

Внедрение в проект PreCSS

$small-width: 960px;
$large-width: calc($small-width + 1px);

@custom-media --small (max-width: $small-width);
@custom-media --large (min-width: $large-width);

.cards {
  @media (--small) {
    width: auto;
  }

  &&_narrow {
    @media (--large) {
      width: $small-width;
    }
  }
}
@media (max-width: 960px) {
  .cards {
    width: auto;
  }
}

@media (min-width: 961px) {
  .cards.cards_narrow {
    width: 960px;
  }
}

Переменные

covers/variables.jpg

Переменные postcss-simple-vars

@import 'config/icons.css';

.icon {
  &_small  { size: $icon-s-size; }
  &_medium { size: $icon-m-size; }
  &_large  { size: $icon-l-size; }
}

Переменные Конфигурация в JavaScript

import sizes from 'config/icons';

export default simpleVars({
  'icon-s-size': toPx(sizes.small),
  'icon-m-size': toPx(sizes.medium),
  'icon-l-size': toPx(sizes.large)
});

Переменные Миксины и плагины

import step from 'config/step';

postcss.plugin('postcss-default-step', () => {
  /* реализация плагина */
});

Переменные Клиентский код

import screen from 'config/screen';

const DesktopOnlyMixin = {
  /* реализация миксина для React */
};

Миксины

covers/mixins.jpg

Миксины postcss-define-property

size: $height $width {
  height: $height;
  width: $width;
}

size: $size {
  height: $size;
  width: $size;
}
.rectangle {
  size: 50px 100px;
}

.square {
  size: 50px;
}

Миксины postcss-mixins

@define-mixin icon $name {
  .icon.icon_$(name) {
    background: url(icons/$(name).svg);
    @mixin-content;
  }
}

@mixin icon twitter {
  width:  16px;
  height: 16px;
}
.icon.icon_twitter {
  background: url(icons/twitter.svg);
  width:      16px;
  height:     16px;
}

Миксины Реализация на JavaScript

require('postcss-mixins')({
  mixins: {
    clearfix () {
      return {
        '&::after': {
          content: '""';
          display: 'table';
          clear:   'both';
        }
      };
    }
  }
});
.column {
  @mixin clearfix;
}

.column::after {
  content: "";
  display: table;
  clear:   both;
}

Миксины Реализация как функции

require('postcss-mixins')({
  mixins: {
    mixinName (mixinAST, ...mixinArgs) {
      /* реализация миксина */
    }
  }
});

Миксины Миксины в PostCSS

Кастомные свойства     →     Миксины     →     Плагины

.example {
  size: 10px 20px;     /* Кастомное свойство */
  @mixin icon twitter; /* Миксин */
  clear: fix;          /* Плагин */
}

Скажем каскаду «Нет!»

covers/cascad.jpg

Скажем каскаду «Нет!» postcss-bem

@component button {
  font-size: 12px;

  @modifier large {
    font-size: 16px;
  }

  @descendent icon {
    margin-right: 6px;
  }
}
.button {
  font-size: 12px;
}

.button_large {
  font-size: 16px;
}

.button__icon {
  margin-right: 6px;
}

Скажем каскаду «Нет!» postcss-bem-linter

/* @define Button */

.Button             { }
.Button--large      { }
.Button-icon        { }
.Button.is-disabled { }

Скажем каскаду «Нет!» CSS Modules

.className {
  color: green;
}
import styles from './style.css';

return `<div class="${ styles.className }">`;

Скажем каскаду «Нет!» react-easy-style

:local .Button {
  font-size: 12px;

  &:focus {
    box-shadow: 0 0 1px 5px #999;
  }

  /* className--propsKey-propsValue */

  &--kind-default { }
  &--kind-success { }
  &--kind-warning { }
}
import css from './button.css';

@EasyStyle(css)
class Button extends React.Component {
  static defaultProps = {
    kind: 'default'
  }

  render () {
    return (
      <button>{ this.props.label }</button>
    );
  }
}

Будущее уже здесь

covers/future.jpg

Будущее уже здесь cssnext

:root {
  --mainFontSize: 12px;
}

@custom-selector :--button button, .button

:--button {
  font-size: var(--mainFontSize);
}

Будущее уже здесь flexbugs и postcss-flexbugs-fixes

div {
  display: flex;
  min-height: 50vh;
}
div {
  display: flex;
  height: 50vh;
}

Зависимости

covers/dependencies.jpg

Зависимости postcss-import

@import 'normalize.css/normalize';
@import 'mobile/index.css' (max-width: 25em);
/* содержимое './node_modules/normalize.css/normalize.css' */

@media (max-width: 25em) {
  /* содержимое mobile/index.css */
}

Зависимости postcss-url

.icon    { background: url(small-image.png); }
.backing { background: url(large-image.png); }
.icon    { background: url(data:image/png;base64,...); }
.backing { background: url(/assets/large-image.png); }

Зависимости postcss-font-magician

body {
  font-family: 'Roboto';
}
@font-face {
  /* определение Roboto */
}

body {
  font-family: 'Roboto';
}

Зависимости webpcss

.icon { background-image: url(images/icon.png); }
.icon       { background-image: url(images/icon.png); }
.webp .icon { background-image: url(images/icon.webp); }

Сжатие

covers/compression.jpg

Сжатие clean-css

clean-css.svg

Сжатие cssnano

cssnano.svg

Организация кода

covers/directories.jpg

Организация кода Структура директорий

/postcss
  |- /config
  |- /mixins
  |- index.js       экспорт сконфигурированного списка плагинов
  |- media.js       экспорт кастомных медиавыражений
  |- mixins.js      экспорт миксин
  |- utils.js       вспомогательные функции
  |- variables.js   экспорт переменных

Организация кода Структура тестов

/test
  |- /fixtures
  |    |- postcss
  |- /postcss
  |    |- /config
  |    |- /mixins
  |    |- index.js         тесты экспорта плагинов
  |    |- integration.js   тесты интеграции между плагинами

Вопросы

covers/questions.jpg